﻿/**************************************************************************
 * Copyright 2013 Arjen van Efferen / arjenvanefferen@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *************************************************************************/
using CommonUtils;
using log4net;
using MudEngine.Collections;
using MudEngine.Parser;
using MudEngine.Parsing;
using MudEngine.Parsing.AST;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace MudEngine.Runtime
{
    public delegate void StackEventHandler(object sender, StackEventArgs args);
    public delegate void DebugEventHandler(object sender, DebugEventArgs args);


	public class Interpreter
	{
        /// <summary>
        /// 
        /// </summary>
        public event StackEventHandler OnStackChanged;
        /// <summary>
        /// 
        /// </summary>
        public event DebugEventHandler OnInstructionChanged;
        /// <summary>
        /// 
        /// </summary>
        private static ILog _log = Log.GetLogger(typeof(Interpreter));
		/// <summary>
		/// Maximum execution time allowed in milliseconds
		/// </summary>
		private TimeSpan MAX_EXECUTION_TIME = new TimeSpan(0, 0, 0, 0, MudContext.INT_MAX_EXEC_TIME);
        /// <summary>
        /// TODO: TEMPORARY SOLUTION towards context switching enabled interpreter (multithreading, CPU limiter on running programs)
        /// Probably not going to happen anytime soon.
        /// There are synchronous calls made to scripted functions from within the driver. These will cause problems when you need context switching in place.
        /// </summary>
        public RuntimeContext Context { get; set; }


        private CommandStringGrammar _grammar;
        private CommandStringParser _parser;
        /// <summary>
        /// 
        /// </summary>
        private bool _debugEnabled = false;
        private int _debugBreakpoint = -1;
        private bool _debugBreakNext = false;

        private List<DelegatedFunction> _runtimeFunctions;

        public Interpreter()
        {
            // TODO: TEMPORARY SOLUTION towards context switching enabled interpreter (multithreading, CPU limiter on running programs)
            this.Context = new RuntimeContext();

            _runtimeFunctions = new List<DelegatedFunction>();
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_write, Name = "write", NumArguments = 1, Type = AType.TYPE_VOID, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_writeln, Name = "writeln", NumArguments = 1, Type = AType.TYPE_VOID, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_new, Name = "new", NumArguments = 1, Type = AType.TYPE_OBJECT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_destruct, Name = "destruct", NumArguments = 1, Type = AType.TYPE_VOID, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_this_object, Name = "this_object", NumArguments = 0, Type = AType.TYPE_OBJECT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_this_player, Name = "this_player", NumArguments = 0, Type = AType.TYPE_OBJECT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_exec, Name = "exec", NumArguments = 2, Type = AType.TYPE_VOID, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_call_other, Name = "call_other", NumArguments = 2, Type = AType.TYPE_VAR, Flags = FunctionFlags.Runtime | FunctionFlags.VarArgs });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_getid, Name = "getid", NumArguments = 1, Type = AType.TYPE_STRING, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_isarray, Name = "isarray", NumArguments = 1, Type = AType.TYPE_BOOL, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_ismap, Name = "ismap", NumArguments = 1, Type = AType.TYPE_BOOL, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_isstring, Name = "isstring", NumArguments = 1, Type = AType.TYPE_BOOL, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_isnumber, Name = "isnumber", NumArguments = 1, Type = AType.TYPE_BOOL, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_isobject, Name = "isobject", NumArguments = 1, Type = AType.TYPE_BOOL, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_typeof, Name = "typeof", NumArguments = 1, Type = AType.TYPE_STRING, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_sizeof, Name = "sizeof", NumArguments = 1, Type = AType.TYPE_INT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_move_object, Name = "move_object", NumArguments = 1, Type = AType.TYPE_VAR, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_set_prevent_destroy, Name = "set_prevent_destroy", NumArguments = 1, Type = AType.TYPE_OBJECT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_explode, Name = "explode", NumArguments = 2, Type = AType.TYPE_ARRAY, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_implode, Name = "implode", NumArguments = 2, Type = AType.TYPE_STRING, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_get_files, Name = "get_files", NumArguments = 1, Type = AType.TYPE_ARRAY, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_capitalize, Name = "capitalize", NumArguments = 1, Type = AType.TYPE_STRING, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_find_object, Name = "find_object", NumArguments = 1, Type = AType.TYPE_OBJECT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_load_object, Name = "load_object", NumArguments = 1, Type = AType.TYPE_OBJECT, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_parser_add_rule, Name = "parser_add_rule", NumArguments = 2, Type = AType.TYPE_VAR, Flags = FunctionFlags.Runtime });
            _runtimeFunctions.Add(new DelegatedFunction() { Index = _runtimeFunctions.Count, Function = f_parse_command, Name = "parse_command", NumArguments = 1, Type = AType.TYPE_ARRAY, Flags = FunctionFlags.Runtime });

            Thread t = new Thread(new ThreadStart(StartNewStaThread));
            t.SetApartmentState(ApartmentState.STA);
            //t.Start();


            _grammar = new CommandStringGrammar();
            _parser = new CommandStringParser(_grammar);
        }

        private void StartNewStaThread() { 
            Application.Run(new DebugForm(this)); 
        } 
        /// <summary>
        /// 
        /// </summary>
        public ObjectManager ObjectManager { get; set; }
        /// <summary>
        /// 
        /// </summary>
		public List<DelegatedFunction> RuntimeFunctions
		{
			get
			{
				return _runtimeFunctions;
			}
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="obj"></param>
		public void Call___INIT(object_t obj)
		{
            Program prog = obj.Program;
			// ___INIT is always last function in program
			Function initFunc = obj.Program.FunctionTable[obj.Program.FunctionTable.Count - 1];
            if (!initFunc.Name.Equals(Function.INIT_FUNC))
                return;

            this.Context.PushControlStack(FrameKind.Function);
            this.Context.CurrentProgram = prog;
            this.Context.TopOfControlStack.NumLocalVariables = 0;

            // setup new frame for local variables
            this.Context.SetupNewFrame(obj.Program.FunctionTable.Count - 1);
            this.Context.PreviousObject = this.Context.CurrentObject;
            this.Context.CurrentObject = obj;

            int e_sp = this.Context.SP;

            // Jump to function entry point
            this.Context.ProgramCounter = initFunc.Address;

            int e_csp = this.Context.CSP;
            this.Eval();
            Assert.IsTrue(e_csp - 1 == this.Context.CSP, "Corrupt CSP after INIT had been called");

            // Remove return value. All functions always return a value on the stack.
            this.Context.Pop();

            Assert.IsTrue(e_sp == this.Context.SP, "Corrupt stack pointer");
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="function"></param>
        /// <param name="numArguments"></param>
        /// <returns></returns>
        private bool ApplyLow(object_t obj, string function, int numArguments)
        {
            InheritedEntry ie = obj.Program.FindFunctionInherited(function);
            if (ie == null)
            {
                this.Context.Pop_N_Elements(numArguments);
                return false;
            }
            Program prog = ie.Program;
            Function func = ie.Function;

            this.Context.PushControlStack(FrameKind.Function);
            this.Context.CurrentProgram = prog;

            this.Context.TopOfControlStack.NumLocalVariables = numArguments;
            /*
        entry->variable_index_offset = variable_index_offset = vio;
        entry->function_index_offset = function_index_offset = fio;
             */
            // setup new frame for local variables
            this.Context.SetupVariables(this.Context.TopOfControlStack.NumLocalVariables, func.NumArguments, func.NumLocals);

            this.Context.PreviousObject = this.Context.CurrentObject;
            this.Context.CurrentObject = obj;
            this.Context.ProgramCounter = func.Address;

            int e_csp = this.Context.CSP;
            bool res = this.Eval();
            Assert.IsTrue(e_csp - 1 == this.Context.CSP, "Corrupt CSP after ApplyLow had been called");

            return res;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="function"></param>
        /// <param name="numArguments"></param>
        /// <returns></returns>
        public Value Apply(object_t obj, string function, int numArguments)
        {
            int e_sp = this.Context.SP - numArguments;
            if (!ApplyLow(obj, function, numArguments))
                return false;

            Value ret_value = this.Context.Pop();

            Assert.IsTrue(e_sp == this.Context.SP, "Corrupt stack pointer");
            return ret_value;
        }

        /// <summary>
		/// Evaluate program at given position. Arguments to function on given address should be pushed on the stack already.
		/// </summary>
		/// <param name="program"></param>
		private bool Eval()
		{

            // Next return will break out of this function
            Context.TopOfControlStack.FrameKind |= FrameKind.External;

			DateTime lastTime = DateTime.Now;
            this.FireInstructionChangedEvent();

			while(true)
			{
				if (!_debugEnabled && (DateTime.Now - lastTime) > MAX_EXECUTION_TIME)
				{
					Log.Info(this.GetType(), "Script out of time. Terminated.");
#warning 'Must implement stack clean-up before returning to caller'

					//return;
				}
				lastTime = DateTime.Now;
                this.FireInstructionChangedEvent();

                if (_debugBreakNext)
                {
                    _debugBreakNext = false;
                    _debugBreakpoint = Context.ProgramCounter;
                    continue;
                }
                else if (_debugEnabled && _debugBreakpoint == Context.ProgramCounter)
                {
                    Thread.Sleep(100);
                    continue;
                }

                OpCodes opCode = Context.CurrentProgram.ReadOpCode(Context.ProgramCounter++);
                if (!this.ExecInstr(opCode))
                    break;
			}
            return true;
		}
        /// <summary>
        /// Executes single instruction
        /// </summary>
        /// <param name="opCode"></param>
        private bool ExecInstr(OpCodes opCode)
        {
            byte _pc_i1_val = 0;
            short _pc_i2_val = 0;
            int _pc_i4_val = 0;
            long _pc_i8_val = 0;
            string _str_val = string.Empty;
            Value _rt_val = null;
            Function func = null;
            Value _rt_idx_s = null;

            //Log.Debug(this.GetType(), string.Format("{0}:{1:X} {2}", Context.CurrentProgram.Filename, Context.ProgramCounter - 1, opCode));

            switch (opCode)
            {
                case OpCodes.ldc_i1_0: Context.Push(Value.constI1_0); break;
                case OpCodes.ldc_i1_1: Context.Push(Value.constI1_1); break;
                case OpCodes.ldc_i2_0: Context.Push(Value.constI2_0); break;
                case OpCodes.ldc_i2_1: Context.Push(Value.constI2_1); break;
                case OpCodes.ldc_i4_0: Context.Push(Value.constI4_0); break;
                case OpCodes.ldc_i4_1: Context.Push(Value.constI4_1); break;
                case OpCodes.ldc_i2:
                    _pc_i2_val = Context.Load2();
                    Context.Push(_pc_i2_val);
                    break;
                case OpCodes.ldc_i4:
                    _pc_i4_val = Context.Load4();
                    Context.Push(_pc_i4_val);
                    break;
                case OpCodes.ldc_i8:
                    _pc_i8_val = Context.Load8();
                    Context.Push(_pc_i8_val);
                    break;
                case OpCodes.lstr:
                    _pc_i4_val = Context.Load4();
                    _str_val = Context.CurrentProgram.StringTable[_pc_i4_val];
                    Context.Push(_str_val);
                    break;
                case OpCodes.lglob:
                    _pc_i4_val = Context.Load4();
                    _rt_val = Context.CurrentObject.Variables[_pc_i4_val + Context.VariableIndexOffset];
                    Context.Push(_rt_val);
                    break;
                case OpCodes.sglob:
                    _pc_i4_val = Context.Load4();
                    _rt_val = Context.Pop();

                    Context.CurrentObject.Variables[_pc_i4_val + Context.VariableIndexOffset] = _rt_val;
                    break;
                case OpCodes.larg:
                case OpCodes.lloc:
                    _pc_i4_val = Context.Load4();

                    _rt_val = Context.GetStackValue(Context.FramePointer + _pc_i4_val);
                    Context.Push(_rt_val);
                    break;
                case OpCodes.sarg:
                case OpCodes.sloc:
                    _pc_i4_val = Context.Load4();
                    _rt_val = Context.Pop();

                    Context.SetStackValue(Context.FramePointer + _pc_i4_val, _rt_val);
                    break;
                case OpCodes.call:
                    // function offset
                    _pc_i2_val = Context.Load2();
                    // TODO: verify below
                    _pc_i2_val += (short)Context.FunctionIndexOffset;

                    if (Context.CurrentObject.Program.FunctionTable[_pc_i2_val].Flags.HasFlag(FunctionFlags.Prototype))
                        Error("Undefined function {0}", Context.CurrentObject.Program.FunctionTable[_pc_i2_val].Name);

                    // Push control stack
                    Context.PushControlStack(FrameKind.Function);
                    Context.CurrentProgram = Context.CurrentObject.Program;
                    Context.TopOfControlStack.NumLocalVariables = Context.Load1();
                    // Find the real function of it is inherited
                    func = Context.SetupNewFrame(_pc_i2_val);
                    // Store final pc in control stack
                    Context.TopOfControlStack.ProgramCounter = Context.ProgramCounter;
                    // Set pc to start of function
                    Context.ProgramCounter = func.Address;

                    break;
                case OpCodes.call_i:
                    // get inherit object
                    Inherit inherit = Context.CurrentProgram.Inherits[Context.Load1()];
                    Program tempProgram = inherit.Program;

                    // function offset
                    _pc_i2_val = Context.Load2();

                    Context.PushControlStack(FrameKind.Function);
                    Context.TopOfControlStack.NumLocalVariables = Context.Load1();
                    Context.CurrentProgram = tempProgram;

                    Context.FunctionIndexOffset += inherit.FunctionIndexOffset;
                    Context.VariableIndexOffset += inherit.VariableIndexOffset;

                    // setup new frame for local variables
                    func = Context.SetupInheritedFrame(_pc_i2_val);
                    Context.TopOfControlStack.ProgramCounter = Context.ProgramCounter;
                    Context.ProgramCounter = func.Address;

                    break;
                case OpCodes.call_virt:
                    _pc_i2_val = Context.Load2();
                    Context.TopOfControlStack.NumLocalVariables = Context.Load1();

                    if (Context.CurrentObject.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                    {
                        Context.Pop_N_Elements(Context.TopOfControlStack.NumLocalVariables);
                        Context.PushUndefined();
                    }
                    else
                        RuntimeFunctions[_pc_i2_val].Function();

                    break;
                case OpCodes.ret:
                    if (Context.TopOfControlStack.FrameKind.HasFlag(FrameKind.Catch))
                    {
                        int k = 0;
                    }
                    // save pushed return value of stack
                    Value returnValue = Context.Pop();

                    // TODO: Only Arguments to the function should be dereferenced when popped of the stack. Local variables should not be dereferenced
                    int numElementsToClean = (Context.SP - Context.FramePointer + 1);
                    if (numElementsToClean > 0)
                        Context.Pop_N_Elements(numElementsToClean);

                    // put return value back on stack
                    Context.Push(returnValue);
                    Assert.IsTrue(Context.SP == Context.FramePointer, "Bad stack at [RETURN]\n");

                    // Restore all important data
                    Context.PopControlStack();

                    // Drop out of eval function when called from external frame
                    if (Context.ControlStack[Context.CSP + 1].FrameKind.HasFlag(FrameKind.External))
                        return false;
                    break;
                case OpCodes.catch_s:
                    _pc_i4_val = Context.Load4();

                    ErrorContext econ = new ErrorContext();
                    Context.SaveContext(econ);

                    Context.PushControlStack(FrameKind.Catch);
                    Context.TopOfControlStack.ProgramCounter = _pc_i4_val;

                    // invoke the next instructions within the boundaries of a Catch frame
                    try
                    {
                        this.Eval();
                    }
                    catch (RuntimeException re)
                    {
                        Context.RestoreContext(econ);
                        Context.Push(re.Message);
                    }

                    Context.PopContext(econ);

                    //if (Context.ControlStack[Context.CSP + 1].FrameKind.HasFlag(FrameKind.External))
                    //    return false;

                    break;
                case OpCodes.catch_e:
                    Context.PopControlStack();
                    Context.Push(Value.constI1_0);
                    // return to the CATCH_START opcode
                    return false;
                case OpCodes.acol:
                    _pc_i1_val = Context.Load1();
                    Mapping p = new Mapping(_pc_i1_val);

                    int b_sp = Context.SP - (_pc_i1_val * 2) + 1;
                    for (int k = 0; k < (_pc_i1_val * 2); k++)
                        p.Add(Context.GetStackValue(b_sp + (k++)), Context.GetStackValue(b_sp + k));

                    Context.Pop_N_Elements(_pc_i1_val * 2);
                    Context.Push(p);

                    break;
                case OpCodes.col:
                    _pc_i1_val = Context.Load1();
                    ValueArray l = new ValueArray(_pc_i1_val);
                    int b_sp1 = Context.SP - _pc_i1_val + 1;
                    int k1 = 0;
                    while (k1 < _pc_i1_val)
                        l.Add(Context.GetStackValue(b_sp1 + (k1++)));
                    Context.Pop_N_Elements(_pc_i1_val);
                    Context.Push(l);
                    break;
                case OpCodes.stelem:
                    _rt_val = Context.Pop();
                    _rt_idx_s = Context.Pop();
                    Value arrayElem = Context.Pop();
                    switch (arrayElem.Type)
                    {
                        case ValueType.STRING:
                            Error("Indexer cannot be assigned to. A string is read-only.");
                            break;
                        case ValueType.MAP:
                            if (_rt_idx_s.IsNumber)
                                arrayElem.Map[_rt_idx_s.Int] = _rt_val;
                            else if (_rt_idx_s.Type == ValueType.STRING)
                                arrayElem.Map[_rt_idx_s] = _rt_val;
                            else
                                Error("Index to a mapping can only be a number or a key (string).");
                            break;
                        case ValueType.ARRAY:
                            if (!_rt_idx_s.IsNumber)
                                Error("Index to an array can only be a number.");
                            arrayElem.Map[_rt_idx_s.Int] = _rt_val;
                            break;
                        default:
                            Error("Wrong type at indexer. Got {0}.", arrayElem.Type);
                            break;
                    }
                    break;
                case OpCodes.idx_e:
                    _i_index(true);
                    break;
                case OpCodes.idx_n:
                    _i_index(false);
                    break;
                case OpCodes.idx_ee:
                    _i_index(true, true);
                    break;
                case OpCodes.idx_en:
                    _i_index(true, false);
                    break;
                case OpCodes.idx_ne:
                    _i_index(false, true);
                    break;
                case OpCodes.idx_nn:
                    _i_index(false, false);
                    break;
                case OpCodes.jmp:
                    Context.ProgramCounter = Context.CurrentProgram.Load4(Context.ProgramCounter);
                    break;
                case OpCodes.jmpf:
                    _pc_i4_val = Context.Load4();

                    _rt_val = Context.Pop();
                    if (_rt_val.IsFalseValue())
                        Context.ProgramCounter = _pc_i4_val;
                    break;
                case OpCodes.jmpt:
                    _pc_i4_val = Context.Load4();

                    _rt_val = Context.Pop();
                    if (_rt_val.IsTrueValue())
                        Context.ProgramCounter = _pc_i4_val;
                    break;
                case OpCodes.dup:
                    Context.Push(Context.Peek());
                    break;
                case OpCodes.ceq:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Ceq(_rt_val, Context.Pop()));
                    break;
                case OpCodes.cneq:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Cneq(_rt_val, Context.Pop()));
                    break;
                case OpCodes.gt:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Gt(Context.Pop(), _rt_val));
                    break;
                case OpCodes.gte:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Gte(Context.Pop(), _rt_val));
                    break;
                case OpCodes.lt:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Lt(Context.Pop(), _rt_val));
                    break;
                case OpCodes.lte:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Lte(Context.Pop(), _rt_val));
                    break;
                case OpCodes.sub:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Sub(Context.Pop(), _rt_val));
                    break;
                case OpCodes.add:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Add(Context.Pop(), _rt_val));
                    break;
                case OpCodes.div:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Div(Context.Pop(), _rt_val));
                    break;
                case OpCodes.mul:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Mul(Context.Pop(), _rt_val));
                    break;
                case OpCodes.mod:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Mod(Context.Pop(), _rt_val));
                    break;
                case OpCodes.cand:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Cand(Context.Pop(), _rt_val));
                    break;
                case OpCodes.cor:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Cor(Context.Pop(), _rt_val));
                    break;
                case OpCodes.and:
                    _rt_val = Context.Pop();
                    Context.Push(Value.And(Context.Pop(), _rt_val));
                    break;
                case OpCodes.xor:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Xor(Context.Pop(), _rt_val));
                    break;
                case OpCodes.or:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Or(Context.Pop(), _rt_val));
                    break;
                case OpCodes.shl:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Shl(Context.Pop(), _rt_val));
                    break;
                case OpCodes.shr:
                    _rt_val = Context.Pop();
                    Context.Push(Value.Shr(Context.Pop(), _rt_val));
                    break;
                case OpCodes.pop:
                    Context.Pop();
                    break;
                case OpCodes.nop:
                    break;
                case OpCodes.eof:
                    return false;
                case OpCodes.bp:
                    Context.ProgramCounter--;
                    break;
                default: throw new Exception("illegal opcode: " + opCode);
            }

            return true;
        }

        #region helper functions
        /// <summary>
        /// TODO: Implement protection agains IndexOutOfBoundsException
        /// </summary>
        /// <param name="end"></param>
        private void _i_index(bool end)
        {
            Value _rt_idx_s = Context.Pop();
            Value _rt_val = Context.Pop();

            switch (_rt_val.Type)
            {
                case ValueType.STRING:
                    if (!_rt_idx_s.IsNumber)
                        Error("Index to a string can only be a number.");
                    if (end)
                        Context.Push(_rt_val.String[_rt_val.String.Length - _rt_idx_s.Int]);
                    else
                        Context.Push(_rt_val.String[_rt_idx_s.Int]);
                    break;
                case ValueType.MAP:
                    if (_rt_idx_s.IsNumber)
                    {
                        if (end)
                            Context.Push(_rt_val.Map[_rt_val.Map.Count - _rt_idx_s.Int]);
                        else
                            Context.Push(_rt_val.Map[_rt_idx_s.Int]);
                    }
                    else if (_rt_idx_s.Type == ValueType.STRING)
                    {
                        if (end)
                            Error("Ranged index to a mapping can only be a number.");
                        Context.Push(_rt_val.Map[_rt_idx_s]);
                    }
                    else
                        Error("Index to a mapping can only be a number or a key (string).");
                    break;
                case ValueType.ARRAY:
                    if (!_rt_idx_s.IsNumber)
                        Error("Index to an array can only be a number.");
                    if (end)
                        Context.Push(_rt_val.Array[_rt_val.Array.Count - _rt_idx_s.Int]);
                    else
                        Context.Push(_rt_val.Array[_rt_idx_s.Int]);
                    break;
                default:
                    Error("Type {0} cannot be indexed.", Enum.GetName(typeof(ValueType), _rt_val.Type));
                    break;
            }

        }

        private void _i_index(bool s_end, bool e_end)
        {
            Value _rt_idx_e = Context.Pop();
            Value _rt_idx_s = Context.Pop();
            Value _rt_val = Context.Pop();

            if (!_rt_idx_s.IsNumber || !_rt_idx_e.IsNumber)
                Error("Ranges can only be denoted by numbered indexes.");
            /*
            if (_rt_idx_s.Int > _rt_idx_e.Int)
                Error("End index should be greater or equal to the start index");
            */

            int s_idx = -1;
            int e_idx = -1;

            switch (_rt_val.Type)
            {
                case ValueType.STRING:
                    if (s_end)
                        s_idx = _rt_val.String.Length - _rt_idx_s.Int;
                    else
                        s_idx = _rt_idx_s.Int;
                    if (e_end)
                        e_idx = _rt_val.String.Length - _rt_idx_e.Int;
                    else
                        e_idx = _rt_idx_e.Int;
                    e_idx = (e_idx - s_idx) + 1;

                    //int e_idx = ((_rt_idx_e.Int >= _rt_val.String.Length ? _rt_val.String.Length - 1 : _rt_idx_e.Int) - _rt_idx_s.Int) + 1;
                    Context.Push(_rt_val.String.Substring(s_idx, e_idx));
                    break;
                case ValueType.MAP:
                    if (s_end)
                        s_idx = _rt_val.Map.Count - _rt_idx_s.Int;
                    else
                        s_idx = _rt_idx_s.Int;
                    if (e_end)
                        e_idx = _rt_val.Map.Count - _rt_idx_e.Int;
                    else
                        e_idx = _rt_idx_e.Int;
                    Context.Push(_rt_val.Map.Submap(s_idx, e_idx));
                    break;
                case ValueType.ARRAY:
                    if (s_end)
                        s_idx = _rt_val.Array.Count - _rt_idx_s.Int;
                    else
                        s_idx = _rt_idx_s.Int;
                    if (e_end)
                        e_idx = _rt_val.Array.Count - _rt_idx_e.Int;
                    else
                        e_idx = _rt_idx_e.Int;
                    Context.Push(_rt_val.Array.Subarray(s_idx, e_idx));
                    break;
                default:
                    Error("Type {0} cannot be indexed.", Enum.GetName(typeof(ValueType), _rt_val.Type));
                    break;
            }
        }

        #endregion

        #region Error handling

        public void Error(string message, params object[] args)
        {
            Type errorType = this.GetType();
            if (Context.CurrentObject != null && Context.CurrentObject.IsInteractive)
            {
                Context.CurrentObject.TcpConnection.SendData(string.Format("* {0}\r\n", string.Format(message, args)));
                errorType = Context.CurrentObject.GetType();
            }
            Log.Debug(errorType, string.Format("* {0}", string.Format(message, args)));
            throw new RuntimeException("* {0}", string.Format(message, args));
        }
        #endregion

        #region Debugger
        private void FireStackChangedEvent()
        {
            /*
            if (this.OnStackChanged != null)
            {
                StackEventArgs args = new StackEventArgs();
                args.Stack = new Value[_sp+1];
                Array.Copy(_stack, args.Stack, _sp+1);
                if (_csStack[_cs] != null)
                {
                    args.Object = currentObject;
                    args.Program = currentProgram;
                }

                this.OnStackChanged(this, args);
            }
            */
        }

        private void FireInstructionChangedEvent()
        {
            /*
            if (this.OnInstructionChanged != null)
            {
                DebugEventArgs args = new DebugEventArgs();
                args.ProgramCounter = programCounter;
                this.OnInstructionChanged(this, args);
            }
            */
        }

        internal void Pause()
        {
            this._debugEnabled = true;
            this._debugBreakNext = true;
        }

        internal void Continue()
        {
            this._debugEnabled = true;
            this._debugBreakpoint = -1;
        }

        internal void RunTo(int breakAt)
        {
            this._debugBreakpoint = breakAt;
            this._debugEnabled = true;
        }

        public void SetBreakPoint(Program prog, int pc)
        {
            if (!prog.SetBreakpoint(pc))
                Log.Info(this.GetType(), string.Format("Position {0} does not point to an OpCode. Breakpoint is ignored.", pc));
        }

        public void StepNext()
        {

        }

        #endregion

        #region Runtime Functions
        /// <summary>
        /// 
        /// </summary>
        public void f_write()
		{
            string message = Context.Pop().Raw.ToString();

            object_t obj = Context.Caller;
            this.writeStringToInteractive(obj, message, false);
            Context.PushOK();
		}

        private void writeStringToInteractive(object_t obj, string message, bool newLine)
        {
            if (obj != null && obj.Flags.HasFlag(ObjectFlags.O_INTERACTIVE))
            {
                if (obj.Flags.HasFlag(ObjectFlags.O_ECHO))
                    f_echo(false);

                obj.TcpConnection.SendData(message);
                if (newLine)
                    obj.TcpConnection.SendData(Telnet.CRLF);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_writeln()
        {
            string message = Context.Pop().Raw.ToString();

            object_t obj = Context.Caller;
            this.writeStringToInteractive(obj, message, true);
            Context.PushOK();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="echo"></param>
        /// <returns></returns>
        public Value f_echo(bool echo)
        {
            object_t obj = Context.Caller;
            if (obj != null && obj.Flags.HasFlag(ObjectFlags.O_INTERACTIVE))
            {
                if (echo != obj.Flags.HasFlag(ObjectFlags.O_ECHO))
                {
                    if (obj.TcpConnection.Send(echo))
                    {
                        if (echo)
                            obj.Flags |= ObjectFlags.O_ECHO;
                        else
                            obj.Flags &= ~ObjectFlags.O_ECHO;
                    }
                }
            }
            return 1;
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_destruct()
		{
            Value val = Context.Pop();
            if (val.Type == ValueType.OBJECT)
                val.Object.Destruct();
            Context.PushUndefined();
		}
        /// <summary>
        /// 
        /// </summary>
        public void f_this_object()
		{
            Context.Push(Context.CurrentObject);
		}
        /// <summary>
        /// 
        /// </summary>
        public void f_this_player()
        {
            Context.Push(Context.Caller);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_new()
		{
            object_t ob = ObjectManager.CloneObject(Context.Pop().String);
            if (ob != null)
                Context.Push(ob);
            else
                Context.PushUndefined();
		}
        /// <summary>
        /// 
        /// </summary>
        public void f_exec()
        {
            int b_sp = Context.SP - Context.TopOfControlStack.NumLocalVariables + 1;
            object_t from = Context.Pop().Object;
            object_t to = Context.Pop().Object;

            if (to.IsInteractive)
                Error("Object to copy to is already an interactive user");
            if (!from.IsInteractive)
                Error("Object to copy from is not an interactive user");

            to.TcpConnection = from.TcpConnection;
            from.TcpConnection = null;
            to.TcpConnection.Object = to;
            to.Flags |= ObjectFlags.O_INTERACTIVE;
            if (from.Flags.HasFlag(ObjectFlags.O_ECHO))
                to.Flags |= ObjectFlags.O_ECHO;
            from.Flags &= ~ObjectFlags.O_INTERACTIVE;

            if (from == Context.Caller)
                Context.Caller = to;

            Context.PushOK();
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_call_other()
        {
            // we can't pop the values from the stack, because above the object and the functionname are the arguments pushed for the call to the 'other' function
            int b_sp = Context.SP - Context.TopOfControlStack.NumLocalVariables + 1;
            object_t ob = null;
            Value fparam = Context.GetStackValue(b_sp);

            string functionName = Context.GetStackValue(b_sp + 1).String;
            if (fparam.Type == ValueType.OBJECT)
                ob = fparam.Object;
            else if (fparam.Type == ValueType.STRING)
            {
                // TODO: Implement interpreter wide error handler
                ob = ObjectManager.FindByName(fparam.String);
                if (ob == null)
                    Error("Cannot call function {0} on {1}. Object could not be found.", functionName, fparam.String); 
            }

            // arguments are already on the stack
            if (!this.ApplyLow(ob, functionName, Context.TopOfControlStack.NumLocalVariables - 2))
            {
                Context.Pop_2_Elements();
                Context.PushUndefines(2);
                Error("Function {0} not defined in {1}.", functionName, ob.Name);
                return;
            }
            // Return value from 'other' is on the stack as are the object and the function we called.
            // Swap the first stack entry with the return value and pop the last 2 elements.
            Context.SetStackValue(Context.SP - 2, Context.GetStackValue(Context.SP));
            Context.Pop_2_Elements();
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_getid()
        {
            object_t obj = null;
            Value param = Context.Pop();
            if (param.Type == ValueType.STRING)
                obj = ObjectManager.FindByName(param.String);
            else if (param.Type == ValueType.OBJECT)
                obj = param.Object;

            Context.Push(obj.Name);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_isarray()
        {
            Value value = Context.Pop();
            Context.Push(value.Type == ValueType.ARRAY);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_ismap()
        {
            Value value = Context.Pop();
            Context.Push(value.Type == ValueType.MAP);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_isstring()
        {
            Value value = Context.Pop();
            Context.Push(value.Type == ValueType.STRING);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_isnumber()
        {
            Value value = Context.Pop();
            Context.Push(value.Type == ValueType.BOOL || value.Type == ValueType.SHORT || value.Type == ValueType.INT || value.Type == ValueType.LONG);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_isobject()
        {
            Value value = Context.Pop();
            Context.Push(value.Type == ValueType.OBJECT);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_typeof()
        {
            Value value = Context.Pop();
            Context.Push(Enum.GetName(typeof(ValueType), value.Type).ToLower());
        }

        public void f_sizeof()
        {
            Value value = Context.Pop();
            Context.Push(value.Size);
        }

        public void f_move_object()
        {
            object_t o1 = null, o2 = null;

            Value param = Context.Pop();
            if (param.Type == ValueType.OBJECT)
                o2 = param.Object;
            else
            {
                o2 = ObjectManager.FindByName(param.String);
                if (o2 == null)
                    Error("Cannot move {0} to {1}. Object could not be found.", o1, o2);
            }
            o1 = Context.CurrentObject;

            o1.Move(o2);

            Context.PushOK();
        }
        /// <summary>
        /// Prevents an object from being garbage collected. The Master and Void object are for example objects that are being garbage collected because the driver depends on their existance.
        /// 
        /// void set_prevent_destroy(object|string)
        /// </summary>
        public void f_set_prevent_destroy()
        {
            object_t o1 = null;
            Value param = Context.Pop();
            if (param.Type == ValueType.OBJECT)
                o1 = param.Object;
            else
            {
                o1 = ObjectManager.FindByName(param.String);
                if (o1 == null)
                    Error("Object {0} could not be found.", o1);
            }
            o1.PreventDestroy = true;

            Context.PushOK();
        }
        /// <summary>
        /// Splits a string and returns an array
        /// 
        /// array explode(string, splitstring)
        /// </summary>
        public void f_explode()
        {
            Value splitString = Context.Pop();
            Value param = Context.Pop();
            if (param.Type == ValueType.STRING && splitString.Type == ValueType.STRING)
            {
                string[] parts = param.String.Split(splitString.String.ToCharArray(), StringSplitOptions.None);
                ValueArray array = new ValueArray(parts.Length);
                foreach(string s in parts)
                    array.Add(s);
                Context.Push(array);
            }
            else
                Error("Bad arguments to 'explode'. Expected 'strings' got {0}", param.Type);
        }
        /// <summary>
        /// Combines all elements in an array and returns it as a string
        /// 
        /// string implode(array, joinstring)
        /// </summary>
        public void f_implode()
        {
            Value joinString = Context.Pop();
            Value array = Context.Pop();
            if (array.Type == ValueType.ARRAY && joinString.Type == ValueType.STRING)
            {
                Context.Push(string.Join<Value>(joinString.String, array.Array));
            }
            else
                Error("Bad arguments to 'implode'. Expected 'array' and 'string', but got {0} and {1}", array.Type, joinString.Type);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_find_object()
        {
            Value param = Context.Pop();
            if (param.Type == ValueType.STRING)
            {
                object_t ob = ObjectManager.FindObject(param.String);
                if (ob != null)
                    Context.Push(ob);
                else
                    Context.PushUndefined();
            }
            else
                Error("Bad argument to 'find_object'. Expected 'string' but got {0}", param.Type);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_load_object()
        {
            Value param = Context.Pop();
            if (param.Type == ValueType.STRING)
            {
                object_t ob = ObjectManager.LoadObject(param.String);
                if (ob != null)
                    Context.Push(ob);
                else
                    Context.PushUndefined();
            }
            else
                Error("Bad argument to 'load_object'. Expected 'string' but got {0}", param.Type);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_get_files()
        {
            Value param = Context.Pop();
            if (param.Type == ValueType.STRING)
            {
                DirectoryInfo di = new DirectoryInfo(MudContext.LIB_DIR + Util.SafePath(param.String));
                if (di.Exists)
                {
                    FileInfo[] fi = di.GetFiles();
                    ValueArray array = new ValueArray(fi.Length);
                    foreach (FileInfo f in fi)
                        array.Add(f.Name);
                    Context.Push(array);
                }
                else
                    Error("Unable to open or read directory '{0}'", param.String);
            }
            else
                Error("Bad arguments to 'get_files'. Expected 'string' got '{0}'", param.Type);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_capitalize()
        {
            Value param = Context.Pop();
            if (param.Type == ValueType.STRING)
            {
                if (param.String.Length < 2)
                    Context.Push(param.String.ToUpper());
                else
                    Context.Push(param.String.Substring(0, 1).ToUpper() + param.String.Substring(1));
            }
            else
                Error("Bad argument to 'capitalize'. Expected 'string' but got '{0}'", param.Type);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_parser_add_rule()
        {
            Value rule = Context.Pop();
            Value verb = Context.Pop();

            if (rule.Type == ValueType.STRING && verb.Type == ValueType.STRING)
            {
                _grammar.AddRule(verb, rule);
                Context.PushOK();
            }
            else
                Error("Bad arguments to 'parser_add_rule'. Expected 'string' and 'string' but got '{0}' and '{1}'", verb.Type, rule.Type);
        }
        /// <summary>
        /// 
        /// </summary>
        public void f_parse_command()
        {
            Value str = Context.Pop();

            TToken[] tts = _parser.ParseString(str);
			if (tts.Length == 0 || (tts.Length == 1 && tts[0].Type == TokenType.ERROR)) // entered not recognized verb
            {
                Context.PushUndefined();
				return;
            }

			int t = 0;
			if (tts[t].Type == TokenType.ERROR)
				t++;

            ValueArray array = new ValueArray(tts.Length - t);
            for (int i = t; i < tts.Length; i++)
            {
                Mapping map = new Mapping();
                map["type"] = tts[i].Type.ToString();
                map["value"] = new Value(tts[i].Value);
                array.Add(map);
            }
            Context.Push(array);

            /*
			string cmd = tts[t++].Value.ToString();
			string verb = This._verbs[cmd.Capitalize()];

			StringBuilder func = new StringBuilder("do_");
			func.Append(cmd);

			List<object> _l = new List<object>();
			for (int i = t; i < tts.Length; i++)
			{
				if (tts[i].Type == TokenType.KEYWORD && !Array.Exists(CommandStringParser.articles, s => s.Equals(tts[i].Value.ToString())))
				{
					func.Append("_str");
					_l.Add(tts[i].Value.ToString());
				}
				else if (tts[i].Type == TokenType.OBJ)
				{
					int num = 1;
					StringCollection adjs = new StringCollection();
					for (int k = i - 1; k >= 0; k--)
					{
						if (tts[k].Type == TokenType.NUMADJ)
							num = (int)tts[k].Value;
						else if (tts[k].Type == TokenType.ADJECTIVE)
							adjs.Add(tts[k].Value.ToString());
					}
					// find object in area
					string noun = tts[i].Value.ToString();
					string[] adjsArray = new string[adjs.Count];
					adjs.CopyTo(adjsArray, 0);
					object obj = environment(this_player()).FindObjectAdjsNum(noun, num, adjsArray);

					_l.Add(obj);
					func.Append("_OBJ");
				}
			}
			object[] funcArgs = new object[_l.Count];
			_l.CopyTo(funcArgs);
            call_other(verb, func.ToString(), funcArgs);

			return true;
             * */
        }

		#endregion
    }

	public class ControlStackContext
	{
        public object_t Ob { get; set; }
        public object_t PreviousOb { get; set; }
		public Program CurrentProgram { get; set; }
		public int ProgramCounter { get; set; }
		public int FramePointer { get; set; }
        public FrameKind FrameKind { get; set; }
        public int VariableIndexOffset { get; set; }
        public int FunctionIndexOffset { get; set; }
        public int NumLocalVariables { get; set; }
    }

    [Flags]
    public enum FrameKind
    {
        Normal = 1,
        External = 2,
        Function = 4,
        Catch = 8
    }

    public class StackEventArgs
    {
        public Value[] Stack { get; set; }

        public object_t Object { get; set; }

        public Program Program { get; set; }
    }

    public class DebugEventArgs
    {
        public int ProgramCounter { get; set; }
    }
}
